﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.VisualStudio.TestTools.UnitTesting;

using zijian666.DI;

namespace UnitTests;

[TestClass]
public class 配置文件执行测试
{
    const string JSON = """
{
  "Empty": {

  },
  "Default": {
    "*": true
  },
  "Full": {
    "A": "a001",
    "B": "b002",
    "C": "c003",
    "D": "d004",
    "E": "e005",
    "F": "f006",
    "K": false
  },
  "Test": {
    "G": "g007"
  },
  "MyConfig": {
    "Str": "Str",
    "Int": 1,
    "Float": 1.1,
    "Bool": true,
    "Array": [
      "a",
      "b",
      "c"
    ],
    "Guid": "00000000-0000-0000-0000-000000000111"
  },
  "Config":{
    "X":{
      "Name":"X",
      "Value":"10"
    },
    "Z":100
  }
}
""";



    [TestMethod]
    public void Test()
    {
        //DiagnosticListener.AllListeners.Subscribe(new DiagnosticListenerObserver());
        //var configuration = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build();
        var host = Host.CreateDefaultBuilder();
        host.ConfigureAppConfiguration(builder =>
        {
            var stream = new System.IO.MemoryStream(Encoding.UTF8.GetBytes(JSON));
            builder.AddJsonStream(stream);
        });
        host.ConfigureServices((context, services) =>
        {
            services.AddLogging(c =>
            {
                c.AddConsole();
            });
        });

        var services = host.Build().Services;
        var configuration = services.GetRequiredService<IConfiguration>();
        var loggerFactory = services.GetRequiredService<ILoggerFactory>();

        {
            var startup = new Startup();
            configuration.Configure("Empty", startup, x => x.LoggerFactory = loggerFactory);

            Assert.AreEqual(null, startup["A"]);
            Assert.AreEqual(null, startup["E"]);
            Assert.AreEqual("g007", startup["H"]);
            Assert.AreEqual(3, startup.Count);
        }
        {
            var startup = new Startup();
            configuration.Configure("Default", startup, x => x.LoggerFactory = loggerFactory);

            Assert.AreEqual(null, startup["A"]);
            Assert.AreEqual(null, startup["B"]);
            Assert.AreEqual("True", startup["D"]);
            Assert.AreEqual("True", startup["E"]);
            Assert.AreEqual("True", startup["F"]);
            Assert.AreEqual("g007", startup["H"]);
            Assert.AreEqual("True", startup["J"]);
            Assert.AreEqual(7, startup.Count);
        }
        {
            var startup = new Startup2();

            configuration.Configure("Full", startup, x => x.LoggerFactory = loggerFactory);

            Assert.AreEqual(null, startup["A"]);
            Assert.AreEqual(null, startup["B"]);
            Assert.AreEqual("c003", startup["C"]);
            Assert.AreEqual("d004", startup["D"]);
            Assert.AreEqual("e005", startup["E"]);
            Assert.AreEqual("f006", startup["F"]);
            Assert.AreEqual("g007", startup["G"]);
            Assert.AreEqual("g007", startup["H"]);
            Assert.AreEqual(null, startup["I"]);
            Assert.AreEqual(9, startup.Count);
        }

        {
            Assert.ThrowsException<InvalidOperationException>(() => configuration.Configure("Full", new Startup3(), x => x.LoggerFactory = loggerFactory));
        }
        {
            var startup4 = new Startup4();
            configuration.Configure(startup4, x => x.LoggerFactory = loggerFactory);
            var config = startup4.Config;
            Assert.AreEqual("Str", config.Str);
            Assert.AreEqual(1, config.Int);
            Assert.AreEqual(1.1f, config.Float);
            Assert.AreEqual(true, config.Bool);
            Assert.AreEqual(3, config.Array?.Length);
            Assert.AreEqual("a", config.Array[0]);
            Assert.AreEqual("b", config.Array[1]);
            Assert.AreEqual("c", config.Array[2]);

            Assert.AreEqual(new Guid("00000000-0000-0000-0000-000000000111"), config.Guid);
        }


        {
            var startup = new Startup5();
            configuration.Configure("Config", startup, x => x.LoggerFactory = loggerFactory);

            Assert.AreEqual("X", startup["X.Name"]);
            Assert.AreEqual(10, startup["X.Value"]);
            Assert.AreEqual(null, startup["Y.Name"]);
            Assert.AreEqual(null, startup["Y.Value"]);
            Assert.AreEqual(100, startup["Z"]);

        }
    }

    class Startup : Dictionary<string, string>
    {
        protected void Invoked(IConfigurationSection section, [CallerMemberName] string? method = null)
            => this[method] = section?.Value;

        [BindConfiguration(true)]
        public void A() => Invoked(null);

        [BindConfiguration(false)]
        public void B() => Invoked(null);


        [BindConfiguration(false)]
        public void D(IConfigurationSection section) => Invoked(section);

        [BindConfiguration(true)]
        public void E(IConfigurationSection section = null) => Invoked(section);

        [BindConfiguration(false)]
        public void F(IConfigurationSection section = null) => Invoked(section);

        [BindConfiguration(true, ConfigurationKeyName = "Test:G")]
        public void H(IConfigurationSection section = null) => Invoked(section);

        [BindConfiguration(false, ConfigurationKeyName = "Test:I")]
        public virtual void J(IConfigurationSection section = null) => Invoked(section);

    }


    class Startup2 : Startup
    {
        [BindConfiguration(true)]
        public void C(IConfigurationSection section) => Invoked(section);

        [BindConfiguration(true, ConfigurationKeyName = "Test:G")]
        public void G(IConfigurationSection section) => Invoked(section);

        [BindConfiguration(true, ConfigurationKeyName = "Test:I")]
        public void I(IConfigurationSection section = null) => Invoked(section);

        [BindConfiguration(true)]
        public void K(IConfigurationSection section = null) => Invoked(section);
    }

    class Startup3 : Startup2
    {
        [BindConfiguration(true, ConfigurationKeyName = "Test:I")]
        public void K(IConfigurationSection section) => Invoked(section);
    }

    class Startup4
    {
        public MyConfig Config { get; set; }
        [BindConfiguration(true)]
        public void MyConfig(MyConfig config)
        {
            Config = config;
        }
    }
    class MyConfig
    {
        public string Str { get; set; }
        public int Int { get; set; }
        public float Float { get; set; }
        public bool Bool { get; set; }
        public string[] Array { get; set; }
        public Guid Guid { get; set; }

    }


    class Startup5 : Dictionary<string, object?>
    {
        [BindConfiguration(true)]
        public void X(Config section)
        {
            this["X.Name"] = section.Name;
            this["X.Value"] = section.Value;
        }

        [BindConfiguration(true)]
        public void Y(Config? section = null)
        {
            this["Y.Name"] = section?.Name;
            this["Y.Value"] = section?.Value;
        }

        [BindConfiguration(true)]
        public void Z(int value)
        {
            this["Z"] = value;
        }
    }

    class Config
    {
        public string Name { get; set; }
        public int Value { get; set; }
    }
}
